package com.examples.gateway.helper.filter;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.examples.gateway.common.CheckState;
import com.examples.gateway.helper.Constants;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName: LogGlobalFilter
 * @Description: TODO
 * @Author: DAIYALU
 * @Date: 2024/5/14
 * @Version: 1.0
 */
@Slf4j
@Component
public class OriginalRequestFilter extends AbstractGlobalHelperFilter {

    private final List<HttpMessageReader<?>> messageReaders;

    public OriginalRequestFilter(ServerCodecConfigurer codecConfigurer) {
        this.messageReaders = codecConfigurer.getReaders();
    }

    // 解析请求参数
    @Override
    public Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Supplier<? extends Mono<? extends Void>> supplier = () -> chain.filter(exchange);
        ServerHttpRequest request = exchange.getRequest();
        String userIp = getClientIp(request);
        log.info("【网关】user IP:{}",userIp);
        exchange.getAttributes().put(Constants.USER_IP, userIp);
        String serverName = (String) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR);
        MediaType mediaType = request.getHeaders().getContentType();
        log.info("【网关】接口请求元数据，服务名：{}，请求类型：{}，方法：{}，路径：{}，路径参数：{}",serverName,Objects.isNull(mediaType) ? "无" : mediaType,
                request.getMethod(),request.getPath(),JSONUtil.toJsonStr(request.getQueryParams().toSingleValueMap()));
        if(Objects.isNull(mediaType)){
            log.info("【网关】组装前的请求参数：{}",JSONUtil.toJsonStr(exchange.getRequest().getQueryParams().toSingleValueMap()));
            return chain.filter(exchange);
        }else if (mediaType.toString().contains(MediaType.APPLICATION_JSON_VALUE)) {
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .map(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        DataBufferUtils.release(dataBuffer);
                        return bytes;
                    }).defaultIfEmpty(new byte[0])
                    .flatMap(bytes -> {
                        log.info("【网关】组装前的请求参数：{}",new String(bytes, StandardCharsets.UTF_8));
                        ServerHttpRequest newRequest = new ServerHttpRequestDecorator(request.mutate().header(Constants.USER_IP,userIp)
                                .header(HttpHeaders.CONTENT_LENGTH,Long.toString(bytes.length)).build()) {
                            @NotNull
                            @Override
                            public Flux<DataBuffer> getBody() {
                                DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
                                return Flux.just(dataBufferFactory.wrap(bytes));
                            }
                        };
                        return chain.filter(exchange.mutate().request(newRequest).build());
                    }).doOnError(e -> handleError(exchange, CheckState.EXCEPTION_GATEWAY_HELPER));
        }else if(MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType)){
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        DataBufferUtils.release(dataBuffer);
                        String bodyStr = multipartFormDataAnalyze(mediaType.toString(), new String(bytes));
                        return getMono(exchange, chain, bytes, bodyStr,userIp);
                    }).doOnError(e -> handleError(exchange, CheckState.EXCEPTION_GATEWAY_HELPER));
        } else if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)){
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        DataBufferUtils.release(dataBuffer);
                        String bodyStr = urlEncodedFormDataAnalyze(new String(bytes));
                        return getMono(exchange, chain, bytes, bodyStr,userIp);
                    }).doOnError(e -> handleError(exchange, CheckState.EXCEPTION_GATEWAY_HELPER));
        }
        return chain.filter(exchange);
    }

    @NotNull
    private Mono<Void> getMono(ServerWebExchange exchange, GatewayFilterChain chain, byte[] bytes, String bodyStr,String userIp) {
        log.info("【网关】组装前的请求参数：{}",bodyStr);
        Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
            DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
            DataBufferUtils.retain(buffer);
            return Mono.just(buffer);
        });
        ServerHttpRequestDecorator mutatedRequest = newDecorator(exchange,bytes.length,cachedFlux,userIp);
        ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
        return ServerRequest.create(mutatedExchange, messageReaders)
                .bodyToMono(byte[].class)
                .then(chain.filter(mutatedExchange));
    }

    @SneakyThrows
    public static String multipartFormDataAnalyze(String contentType, String bodyString) {
        //获取随机字符传信息
        String boundary = contentType.substring(contentType.lastIndexOf("boundary=") + 9);
        HashMap<String, Object> formMap = Maps.newHashMap();
        /**
         * 根据自己需求进行对bodyString信息修改，例如下面，根据boundary对传入的bodyString进行了分割
         *  String[] split = bodyString.split(boundary);
         *  然后将修改完后的信息封装到formMap中，需要注意的是，file文件需要以new FileResource(file, fileName)的形式当作value放到formMap中
         */
        String part = "^\r\nContent-Disposition: form-data; name=\"([^/?]+)\"\r\n\r\n([^/?]+)\r\n--?$";
        String filePart = "name=\"([^\"]+)\"(?:; filename=\"([^\"]+)\")?";
        Pattern r = Pattern.compile(part);
        Pattern fileR = Pattern.compile(filePart);
        String[] split = bodyString.split(boundary);
        for(int x=1;x<split.length-1;x++){
            Matcher m = r.matcher(split[x]);
            if(m.find()){
                buildParam(formMap, m);
            }else{
                Matcher fileM = fileR.matcher(split[x]);
                if(fileM.find()){
                    buildParam(formMap, fileM);
                }
            }
        }
        return JSONUtil.toJsonStr(formMap);
    }

    private static void buildParam(HashMap<String, Object> formMap, Matcher m) {
        String name = m.group(1);
        String value = m.group(2);
        if(formMap.containsKey(name)){
            if(formMap.get(name) instanceof Collection){
                ((List<Object>) formMap.get(name)).add(value);
            }else{
                formMap.put(name, Lists.newArrayList(formMap.get(name),value));
            }
        }else{
            formMap.put(name,value);
        }
    }

    @SneakyThrows
    public static String urlEncodedFormDataAnalyze(String bodyString) {
        Map<String, Object> formMap = Maps.newHashMap();
        String[] split = bodyString.split("&");
        for (String s : split) {
            String[] split1 = s.split("=");
            formMap.put(split1[0],split1[1]);
        }
        return JSONUtil.toJsonStr(formMap);
    }

    public ServerHttpRequestDecorator newDecorator(ServerWebExchange exchange,long dataLength,Flux<DataBuffer> body,String userIp){
        return new ServerHttpRequestDecorator(
                exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                //数据长度变了以后 需要修改header里的数据，不然接收数据时会异常
                //我看别人说删除会自动补充数据长度，但我这个版本不太行
//				long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                if(StrUtil.isNotBlank(userIp)){
                    httpHeaders.set(Constants.USER_IP,userIp);
                }
                httpHeaders.setContentLength(dataLength);
                return httpHeaders;
            }
            @Override
            public Flux<DataBuffer> getBody() {
                return body;
            }
        };
    }

    private String getClientIp(ServerHttpRequest request) {
        String ip = request.getHeaders().getFirst("X-Forwarded-For");
        if (ip != null && !ip.isEmpty()) {
            // X-Forwarded-For 可能是多个 IP 地址的列表，取第一个
            ip = ip.split(",")[0].trim();
        } else {
            ip = request.getRemoteAddress() != null ? request.getRemoteAddress().getAddress().getHostAddress() : "unknown";
        }
        return ip;
    }

    @Override
    public int getOrder() {
        return -1000;
    }

}
